AWS Amplify Gen 2 でデータの認可を設定した際に AWS リソース上でどのような構成がされているのかを観察してみた

AWS Amplify Gen 2 でデータの認可を設定した際に AWS リソース上でどのような構成がされているのかを観察してみた

Clock Icon2024.08.25

いわさです。

Amplify Gen 2 にはデータモジュールが用意されており、AWS リソースとしては AppSync + DynamoDB でデータアクセス部分のサーバー機能が提供される形となっています。

Amplify Quickstart のテンプレートから開始した場合は次のように Todo の登録が行えるようになっています。
この機能でデータ作成を行うと次のような項目が DynamoDB テーブルに作成されます。

341CE569-E42A-4036-A19E-F64E0356E238.png

{
 "id": "6a9b30d4-b3d2-4fbe-ae0a-439968d4dbd3",
 "content": "hoge",
 "createdAt": "2024-08-19T20:52:02.584Z",
 "updatedAt": "2024-08-19T20:52:02.584Z",
 "__typename": "Todo"
}

データモデル変更してみる

データモデルや詳細設定についてはamplify/data/resource.ts上で設定することが可能です。
まずは一番初歩的なステップとして Todo ではなく独自のデータモデルを複数定義してみます。

amplify/data/resource.ts
import { type ClientSchema, a, defineData } from "@aws-amplify/backend";

const schema = a.schema({
  // Todo: a
  //   .model({
  //     content: a.string(),
  //   })
  //   .authorization((allow) => [allow.publicApiKey()]),
  Hoge: a
    .model({
      HogeContent: a.string(),
      HogeNum: a.integer(),
    })
    .authorization((allow) => [allow.publicApiKey()]),
  Fuga: a
    .model({
      FugaContent: a.string(),
      FugaBool: a.boolean(),
    })
    .authorization((allow) => [allow.guest()])
});

:

変更したのでサンドボックスを更新します。
そして、データモデル構造にユーザーインターフェース側も依存している部分があるので、そのあたりも変更しておきます。

src/App.tsx
import { Authenticator } from '@aws-amplify/ui-react'
import '@aws-amplify/ui-react/styles.css'
import { useEffect, useState } from "react";
import type { Schema } from "../amplify/data/resource";
import { generateClient } from "aws-amplify/data";

const client = generateClient<Schema>();

function App() {
  const [hoges, setHoges] = useState<Array<Schema["Hoge"]["type"]>>([]);

  useEffect(() => {
    client.models.Hoge.observeQuery().subscribe({
      next: (data) => setHoges([...data.items]),
    });
  }, []);

  function createHoge() {
    client.models.Hoge.create({ HogeContent: window.prompt("Hoge content") });
  }

  return (
    <Authenticator>
      {({ signOut }) => (
        <main>
          <h1>My Hoges</h1>
          <button onClick={createHoge}>+ new</button>
          <ul>
            {hoges.map((hoge) => (
              <li key={hoge.id}>{hoge.HogeContent}</li>
            ))}
          </ul>
          <div>
            🥳 App successfully hosted. Try creating a new Hoge.
            <br />
            <a href="https://docs.amplify.aws/react/start/quickstart/#make-frontend-updates">
              Review next step of this tutorial.
            </a>
          </div>
          <button onClick={signOut}>Sign out</button>
        </main>
      )}
    </Authenticator>
  );
}

export default App;

これでサンドボックス + ローカルで実行してみましょう。

4142AAEF-C12B-4849-A480-8844B82FE64B.png

新しく定義したモデルでデータ登録を行うことが出来ました。
この時の AppSync データソース設定を眺めてみます。

50E62769-2C03-4418-818E-2649F721F874.png

モジュール上で定義した Hoge と Fuga の DynamoDB テーブルがそれぞれ作成されていますね。
バックエンドをデプロイした段階で CloudFormation スタックが更新され、AppSync データソース(DynamoDB テーブル)も更新されていることが確認出来ました。

認可を設定した時の挙動

Amplify Gen 2 のデータモデルでは認可の機能が備わっており、次の公式ドキュメントにも記載されています。
データモデルスキーマを宣言する際に、認可設定も行ってやる感じです。テーブルごとに宣言が可能です。

https://docs.amplify.aws/react/build-a-backend/data/customize-authz/

デフォルトだとallow.publicApiKeyが使われており、Cognito の認証状況に関わらず API キーが使用されます。使われる API キーの実体についてはamplify_outputs.jsonに設定されています。

上記ドキュメントを見て頂くと様々な認可オプションが提供されており、かなり柔軟にこのあたりは設定することが可能です。
ただ、この設定を行ったとき実体である AWS リソースではどこでどのような認可制御が行われているのでしょうか。

一応 DynamoDB でも IAM のコンテキストキーを使った制御などは出来ますが、このあたりを使っているのでしょうか。
それとも DynamoDB では特に制御しておらず AppSync で行われているのでしょうか。

https://docs.aws.amazon.com/ja_jp/amazondynamodb/latest/developerguide/specifying-conditions.html

実験してみましょう。

owner 設定

まずはallow.owner()を設定してみます。
併せてdefaultAuthorizationModeを変更する必要があり、ここではuserPoolを設定します。

amplify/data/resource.ts
:
const schema = a.schema({
  // Todo: a
  //   .model({
  //     content: a.string(),
  //   })
  //   .authorization((allow) => [allow.publicApiKey()]),
  Hoge: a
    .model({
      HogeContent: a.string(),
      HogeNum: a.integer(),
    })
    .authorization((allow) => [allow.owner()])
});

export type Schema = ClientSchema<typeof schema>;

export const data = defineData({
  schema,
  authorizationModes: {
    //defaultAuthorizationMode: "apiKey",
    defaultAuthorizationMode: "userPool",
    // API Key is used for a.allow.public() rules
    apiKeyAuthorizationMode: {
      expiresInDays: 30,
    },
  },
});
:

上記変更後、サンドボックスにデプロイしました。
AWS マネジメントコンソール上で AppSync の設定を眺めてみるとプライマリ認可モードと、追加の認可モードの構成が変わったことが確認出来ました。(デプロイ前は API キーがプライマリ認可モードだった)

C8C95259-596E-4FD7-8E52-8E0C994A339E.png

アプリケーション上からデータを1件作成してみます。

9CEB27F3-851B-4C96-BFF8-1C102EB40A62.png

AWS 上のデータやリソースを確認してみましょう。

DynamoDB では項目に属性が追加されている

先ほどまでと異なり、項目の属性にownerが追加されています。

8DA02884-6D42-41F1-87DB-7A12E71CBB86.png

値は UUID を::で結合したような感じです。

{
 "id": "d4c86ceb-ecc7-49d3-a4e8-ec92a91d7771",
 "createdAt": "2024-08-21T21:15:34.291Z",
 "HogeContent": "aaa",
 "owner": "a764cae8-e031-706b-e5ec-09dd86414825::a764cae8-e031-706b-e5ec-09dd86414825",
 "updatedAt": "2024-08-21T21:15:34.291Z",
 "__typename": "Hoge"
}

ただ、DynamoDB で変化が確認出来たのはここまででした。
なので、DynamoDB 上で何か認可やフィルター処理が実装されている感じではなさそうですね。

AppSync ではリゾルバ−でフィルタリング処理が実装されている

色々と眺めていたところ、AppSync のリゾルバ−上で認可情報として設定した内容が反映されていることが確認出来ました。
次は先ほど定義した Hoge モデルに対する List クエリのリゾルバ−です。

CA07ABFB-8D22-4523-ACAC-EC8870A70116_1_105_c.jpeg

なるほど、認可タイプによって挙動が異なるようです。
API キーの場合は条件なしでOK、今回 Cognito ユーザープールを指定しているので ID トークンのクレームを組み合わせて判断してますね。

subusernameを結合し、フィルターとして使用しています。
これがデータ部分の認可ロジック的な感じですね。

さいごに

本日は AWS Amplify Gen 2 でデータの認可を設定した際に AWS リソース上でどのような構成がされているのかを観察してみました。

DynamoDB で属性を保持し、AppSync リゾルバ−で認可モードに応じてフィルターを生成していることがわかりました。Cognito ユーザープールの場合はsubusernameでユニークにしていると。

認可オプションの中にcustomがあったので、Verified Permissions とか使おうとするとそのあたり必要になりそうですね。
マルチテナントを考慮したデータレイヤーの認可を実装したかったのでクレームをカスタマイズ出来ないか次回ためしてみたいと思います。

Share this article

facebook logohatena logotwitter logo

© Classmethod, Inc. All rights reserved.